﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using SharedTypes;
using Microsoft.Xna.Framework;

namespace FrogEngine
{
	public class CollisionModel : ModelGameEntityBase
	{
		public CollisionModel(IEngine engine, string assetName)
			: base(engine, assetName)
		{
			this.Forward = Vector3.Forward;
			this.Right = Vector3.Right;
			this.Up = Vector3.Up;
			this.device = engine.GraphicsDevice;
			vertBuffer = new DynamicVertexBuffer(device, typeof(VertexPositionColor), 150, BufferUsage.None);
			vertData = new VertexPositionColor[150];
			wireEffect = new BasicEffect(device);
			wireEffect.VertexColorEnabled = true;
		}

		private GraphicsDevice device;
		private Octree<Vertex> octree { get; set; }

        //public List<Vertex> Vertices { get; private set; }
        //public List<Vector3> Positions { get; private set; }
        //public List<int> Indices { get; private set; }
		public List<Triangle> Triangles { get; private set; }

		DynamicVertexBuffer vertBuffer;
		VertexPositionColor[] vertData;
		BasicEffect wireEffect;

		private void SetVertData(List<Triangle> triangles)
		{
			var trisToDraw = triangles.Count > 50 ? triangles.Take(50).ToList() : triangles;

			int triCount = 0;
			for (int i = 0; i < 150; triCount++, i += 3)
			{
				if (triCount >= trisToDraw.Count)
				{
					vertData[i + 0] = new VertexPositionColor(Vector3.Zero, Color.White);
					vertData[i + 1] = new VertexPositionColor(Vector3.Zero, Color.White);
					vertData[i + 2] = new VertexPositionColor(Vector3.Zero, Color.White);
				}
				else
				{
					vertData[i + 0] = new VertexPositionColor(Vector3.Transform(trisToDraw[triCount].V1, this.World), Color.White);
					vertData[i + 1] = new VertexPositionColor(Vector3.Transform(trisToDraw[triCount].V2, this.World), Color.White);
					vertData[i + 2] = new VertexPositionColor(Vector3.Transform(trisToDraw[triCount].V3, this.World), Color.White);
				}
			}

			vertBuffer.SetData(vertData);
		}

		public override void Update(GameTime gameTime)
		{
			World = Matrix.CreateScale(28) * Matrix.CreateTranslation(this.Position);

			var camera = this.cameraService.GetActiveCamera();
			List<Triangle> triangles = this.IntersectsMesh(new Ray(camera.Position, camera.Forward));
            this.SetVertData(triangles);
		}

		public override void Render(GameTime gameTime)
		{
			var camera = cameraService.GetActiveCamera();

			//device.RasterizerState = new RasterizerState() { FillMode = Microsoft.Xna.Framework.Graphics.FillMode.WireFrame };
			device.BlendState = BlendState.Opaque;
			device.DepthStencilState = DepthStencilState.Default;

			Matrix[] transforms = new Matrix[this.Model.Bones.Count];
			this.Model.CopyAbsoluteBoneTransformsTo(transforms);
			
			foreach (ModelMesh mesh in this.Model.Meshes)
			{
				foreach (BasicEffect eff in mesh.Effects)
				{
					eff.EnableDefaultLighting();
					eff.World = transforms[mesh.ParentBone.Index] * World;

					eff.View = camera.View;
					eff.Projection = camera.Projection;
				}
				mesh.Draw();
			}

			device.RasterizerState = new RasterizerState() { FillMode = Microsoft.Xna.Framework.Graphics.FillMode.WireFrame };
			device.DepthStencilState = DepthStencilState.None;
			device.SetVertexBuffer(vertBuffer);
			wireEffect.Projection = camera.Projection;
			wireEffect.View = camera.View;
			foreach (var pass in wireEffect.CurrentTechnique.Passes)
			{
				pass.Apply();
				device.DrawPrimitives(PrimitiveType.TriangleList, 0, 50);
			}

			device.RasterizerState = RasterizerState.CullCounterClockwise;
			device.DepthStencilState = DepthStencilState.Default;
		}

		public override void LoadContent()
		{
			var Vertices = new List<Vertex>();
			var Positions = new List<Vector3>();
			var Indices = new List<int>();
			this.Triangles = new List<Triangle>();

			this.Model = ContentManager.Load<Model>(this.modelAsset);

			if (this.Model.Tag == null)
				throw new Exception("Model is not using correct content processor");

			List<object> meshParts = this.Model.Tag as List<object>;
			if (meshParts == null)
				throw new Exception("Data from content processor incorrect for this type");

			int modelVCount = 0;
			int triCount = 0;
			foreach (var mesh in this.Model.Meshes)
				foreach (var meshpart in mesh.MeshParts)
				{
					modelVCount += meshpart.NumVertices;
					triCount += meshpart.PrimitiveCount;
				}

			int vertexCount = 0;
			foreach (var part in meshParts)
			{
				Dictionary<Vector3, Vertex> positionToVertex = new Dictionary<Vector3, Vertex>();
				var meshPart = (MeshPart)part;

				vertexCount = Positions.Count;

				for (int i = 0; i < meshPart.Indices.Count; i += 3)
				{
					int i1 = meshPart.Indices[i];
					int i2 = meshPart.Indices[i + 1];
					int i3 = meshPart.Indices[i + 2];
					var v1 = meshPart.Vertices[i1];
					var v2 = meshPart.Vertices[i2];
					var v3 = meshPart.Vertices[i3];

					Indices.Add(i1 + vertexCount);
					Indices.Add(i2 + vertexCount);
					Indices.Add(i3 + vertexCount);

					Triangle t = new Triangle(v1, v2, v3, i1 + vertexCount, i2 + vertexCount, i3 + vertexCount);
					this.Triangles.Add(t);

					if (positionToVertex.ContainsKey(v1))
						positionToVertex[v1].Triangles.Add(t);
					else
						positionToVertex.Add(v1, new Vertex(v1, new Triangle[] { t }));

					if (positionToVertex.ContainsKey(v2))
						positionToVertex[v2].Triangles.Add(t);
					else
						positionToVertex.Add(v2, new Vertex(v2, new Triangle[] { t }));

					if (positionToVertex.ContainsKey(v3))
						positionToVertex[v3].Triangles.Add(t);
					else
						positionToVertex.Add(v3, new Vertex(v3, new Triangle[] { t }));
				}

				foreach (var vert in meshPart.Vertices)
					Positions.Add(vert);

				HashSet<Triangle> tris = new HashSet<Triangle>();
				foreach (var kvp in positionToVertex)
				{
					foreach (var tri in kvp.Value.Triangles)
					{
						tris.Add(tri);
					}

					Vertices.Add(kvp.Value);
				}
			}

			this.octree = new Octree<Vertex>(Vertices);
		}

		//public bool IntersectsMesh(Ray ray)
		//{
		//    if (this.octree.AABB.Intersects(ray) == null)
		//        return false;

		//    var verts = this.octree.Intersects(ray);
		//    HashSet<Triangle> tris = new HashSet<Triangle>();
		//    foreach (var vert in verts)
		//        foreach (var tri in vert.Triangles)
		//            tris.Add(tri);

		//    foreach (var t in tris)
		//        if (t.Intersects(ray))
		//            return true;

		//    return false;
		//}

		public List<Triangle> IntersectsMesh(Ray ray)
		{
			// transform ray into Model Space
			Matrix inverseTransform = Matrix.Invert(this.World);
			ray.Position = Vector3.Transform(ray.Position, inverseTransform);
			ray.Direction = Vector3.TransformNormal(ray.Direction, inverseTransform);

			if (this.octree.AABB.Intersects(ray) == null)
				return new List<Triangle>();

			var verts = this.octree.Intersects(ray);
			HashSet<Triangle> tris = new HashSet<Triangle>();
			foreach (var vert in verts)
				foreach (var tri in vert.Triangles)
					tris.Add(tri);

			//List<float?> dists = new List<float?>();
			List<Triangle> tr = new List<Triangle>();
			foreach (var t in tris)
			{
				float dis;
				if (t.Intersects(ray, out dis))
					tr.Add(t);
			}

			return tr;
			//return dists.Count == 0 ? null : dists.Select(d => Vector3.Transform(ray.Position + ray.Direction * d.Value, this.World)).ToList();
		}

		public bool CollidesWith(CollisionModel otherModel)
		{
			if (!this.octree.AABB.Intersects(otherModel.octree.AABB))
				return false;

			// TODO
			return false;
		}
	}
}
